# -*- coding: utf-8 -*-
# 白马策略
'''
实现一个策略(适用于红利白马股): 
 参考xxx.py 文件实现, 支持可配置化参考
 1. 选择市盈率pe>=20并且 股息率>=3%的股票 
 2. 使用120日移动平均线MA120作为参考点 
 3. 买入点: 当股价小于MA120的12%买入 
 4. 卖出点: 当股价大于MA120的12%卖出 
 5. 然后将这个策略回测2015年到2025年收益情况(可配置化) 

 最后输出相关文档data文件夹下, 得出: 股票最近的买入点与卖出点,并得出相关结论;

 支持输入多个个红利etf(如159207)然后根据etf中高分红的股票代码进行筛选
 支持手动输入股票代码(可配置化数组)查询得出买卖点: 600900, 601919

 相关参数支持配置化, 如回测时间,MA120的比例等 
 并在根据回测进行优化可能得参数(写好注释) 
 写完代码需要自己测试, 并根据测试结果进行调整
'''

import akshare as ak
import pandas as pd
import numpy as np
import datetime
import os
import argparse
import time
import random
from functools import lru_cache
import matplotlib.pyplot as plt
from matplotlib.font_manager import FontProperties

# 导入安全请求模块
try:
    from akshare_request import (
        get_stock_zh_a_hist_safe,
        get_stock_zh_a_spot_em_safe,
        get_tool_trade_date_hist_sina_safe,
        get_stock_financial_analysis_indicator_safe
    )
except ImportError:
    # 如果没有安全请求模块，使用直接请求
    print("警告: 未找到安全请求模块，使用直接请求可能导致API限流")
    
    def get_stock_zh_a_hist_safe(symbol, period, start_date, end_date, adjust):
        return ak.stock_zh_a_hist(symbol=symbol, period=period, start_date=start_date, end_date=end_date, adjust=adjust)
    
    def get_stock_zh_a_spot_em_safe():
        return ak.stock_zh_a_spot_em()
    
    def get_tool_trade_date_hist_sina_safe():
        return ak.tool_trade_date_hist_sina()
    
    def get_stock_financial_analysis_indicator_safe(symbol):
        return ak.stock_financial_analysis_indicator(symbol=symbol)

# 配置参数
STRATEGY_CONFIG = {
    # 基本筛选条件
    'min_pe': 20,                # 最小市盈率
    'min_dividend_yield': 3.0,   # 最小股息率(%)
    'min_market_value': 1000000000,  # 最小市值（10亿）
    'max_market_value': 500000000000,  # 最大市值（5000亿）
    
    # 移动平均线参数
    'ma_period': 120,            # 移动平均线周期
    'price_ma_ratio': 12,        # 价格与MA的偏离比例(%)
    
    # 回测参数
    'backtest_start_year': 2015,  # 回测开始年份
    'backtest_end_year': 2025,    # 回测结束年份
    
    # 股票池设置
    'include_st': False,          # 是否包含ST股票
    'include_gem': False,         # 是否包含创业板（30开头）
    'include_sci_tech': False,    # 是否包含科创板（68开头）
    'include_main': True,         # 是否包含主板（00、60开头）
    
    # ETF设置
    # 'etf_codes': ['159207'],      # 红利ETF代码列表
    
    # 手动输入的股票代码
    # 'manual_stock_codes': ['600900', '601919'],  # 手动输入的股票代码列表
    'manual_stock_codes': ['600694', '600377','600012','600350','601963'],  # 手动输入的股票代码列表
    
    # 输出设置
    'output_folder': 'data',      # 输出文件夹
    'plot_results': False,         # 是否绘制结果图表
    
    # 优化参数 (可以根据回测结果调整)
    'optimize_params': {
        'ma_periods': [60, 90, 120, 150, 180],  # 不同的移动平均线周期
        'price_ma_ratios': [8, 10, 12, 15, 18]  # 不同的价格与MA的偏离比例(%)
    }
}

def format_date(date_str):
    """格式化日期字符串为YYYYMMDD格式"""
    # 如果输入的是YYYY-MM-DD格式，转换为YYYYMMDD
    if '-' in date_str:
        return date_str.replace('-', '')
    return date_str

def get_stock_list():
    """获取符合条件的A股股票列表，使用安全请求函数"""
    try:
        # 使用安全请求获取A股股票基本信息
        stock_info = get_stock_zh_a_spot_em_safe()
        
        # 根据配置筛选股票代码
        code_filters = []
        if STRATEGY_CONFIG['include_main']:
            code_filters.extend(["00", "60"])
        if STRATEGY_CONFIG['include_gem']:
            code_filters.append("30")
        if STRATEGY_CONFIG['include_sci_tech']:
            code_filters.append("68")
            
        if not code_filters:  # 如果没有选择任何板块，返回空DataFrame
            print("错误：至少需要选择一个板块（主板、创业板或科创板）")
            return pd.DataFrame()
            
        # 筛选股票代码
        stock_list = stock_info[stock_info["代码"].str.startswith(tuple(code_filters))]
        
        # 筛选ST股票
        if not STRATEGY_CONFIG['include_st']:
            stock_list = stock_list[~stock_list["名称"].str.contains("ST")]
        
        # 筛选市值范围
        stock_list["流通市值"] = pd.to_numeric(stock_list["流通市值"], errors="coerce")
        stock_list = stock_list[
            (stock_list["流通市值"] >= STRATEGY_CONFIG['min_market_value']) & 
            (stock_list["流通市值"] <= STRATEGY_CONFIG['max_market_value'])
        ]
        
        return stock_list
    except Exception as e:
        print(f"获取股票列表失败: {e}")
        return pd.DataFrame()

def get_trading_dates(start_date, end_date):
    """获取指定日期范围内的交易日列表"""
    try:
        # 使用安全请求获取交易日历
        trade_cal = get_tool_trade_date_hist_sina_safe()
        
        # 转换日期格式
        start_date_obj = datetime.datetime.strptime(start_date, '%Y%m%d').date()
        end_date_obj = datetime.datetime.strptime(end_date, '%Y%m%d').date()
        
        # 筛选指定日期范围内的交易日
        trade_dates = trade_cal[(trade_cal['trade_date'] >= start_date_obj) & 
                               (trade_cal['trade_date'] <= end_date_obj)]
        
        # 格式化日期为YYYYMMDD
        formatted_dates = []
        for date in trade_dates['trade_date'].tolist():
            date_str = date.strftime('%Y%m%d')
            formatted_dates.append(date_str)
        
        return formatted_dates
    except Exception as e:
        print(f"获取交易日期失败: {e}")
        return []

@lru_cache(maxsize=300)
def get_stock_history(stock_code, start_date, end_date, max_retries=3):
    """获取指定股票在给定时间范围内的历史数据"""
    try:
        # 格式化日期
        start_date = format_date(start_date)
        end_date = format_date(end_date)
        
        # 使用安全请求函数获取历史数据
        df = get_stock_zh_a_hist_safe(
            symbol=stock_code, 
            period="daily", 
            start_date=start_date, 
            end_date=end_date, 
            adjust="qfq"
        )
        
        return df if not df.empty else pd.DataFrame()
    except Exception as e:
        print(f"{stock_code}: 获取历史数据失败 {e}")
        return pd.DataFrame()

def get_stock_financial_data(stock_code):
    """获取股票财务指标数据，包括市盈率和股息率"""
    try:
        # 使用安全请求函数获取财务指标数据
        financial_data = get_stock_financial_analysis_indicator_safe(stock_code)
        
        if financial_data.empty:
            return None
        
        # 获取最新的财务数据
        latest_data = financial_data.iloc[0]
        
        # 提取市盈率和股息率
        pe_ratio = latest_data.get('市盈率(动态)', None)
        dividend_yield = latest_data.get('股息率(%)', None)
        
        # 转换为数值类型
        try:
            pe_ratio = float(pe_ratio) if pe_ratio and pe_ratio != '-' else None
            dividend_yield = float(dividend_yield) if dividend_yield and dividend_yield != '-' else None
        except:
            pe_ratio = None
            dividend_yield = None
        
        return {
            'pe_ratio': pe_ratio,
            'dividend_yield': dividend_yield
        }
    except Exception as e:
        print(f"{stock_code}: 获取财务数据失败 {e}")
        return None

def filter_stocks_by_criteria():
    """根据市盈率和股息率筛选股票"""
    # 获取股票列表
    stock_list = get_stock_list()
    if stock_list.empty:
        print("获取股票列表失败，程序结束")
        return pd.DataFrame()
    
    print(f"共获取 {len(stock_list)} 只符合市值条件的股票，开始筛选市盈率和股息率...")
    
    # 筛选结果
    filtered_stocks = []
    
    # 批量处理，避免请求过于频繁
    batch_size = 50
    total = len(stock_list)
    
    for batch_start in range(0, total, batch_size):
        batch_end = min(batch_start + batch_size, total)
        batch = stock_list.iloc[batch_start:batch_end]
        
        print(f"\n处理批次: {batch_start+1}-{batch_end}/{total}")
        
        for _, row in batch.iterrows():
            stock_code = row["代码"]
            stock_name = row["名称"]
            
            # 获取财务数据
            financial_data = get_stock_financial_data(stock_code)
            if not financial_data:
                continue
            
            pe_ratio = financial_data['pe_ratio']
            dividend_yield = financial_data['dividend_yield']
            
            # 检查是否符合条件
            if (pe_ratio is not None and pe_ratio >= STRATEGY_CONFIG['min_pe'] and 
                dividend_yield is not None and dividend_yield >= STRATEGY_CONFIG['min_dividend_yield']):
                filtered_stocks.append({
                    '代码': stock_code,
                    '名称': stock_name,
                    '市盈率': pe_ratio,
                    '股息率(%)': dividend_yield,
                    '流通市值': row.get('流通市值', None)
                })
                print(f"符合条件: {stock_code} {stock_name}, 市盈率: {pe_ratio:.2f}, 股息率: {dividend_yield:.2f}%")
        
        # 批次间暂停，避免请求过于频繁
        if batch_end < total:
            pause_time = random.uniform(1.0, 3.0)
            print(f"批次处理完成，暂停 {pause_time:.1f} 秒...")
            time.sleep(pause_time)
    
    # 转换为DataFrame
    if not filtered_stocks:
        print("没有符合条件的股票")
        return pd.DataFrame()
    
    df_filtered = pd.DataFrame(filtered_stocks)
    return df_filtered

def get_etf_constituents(etf_code):
    """获取ETF成分股"""
    try:
        # 使用akshare获取ETF成分股
        # 根据ETF代码类型选择不同的函数
        if etf_code.startswith('1'):  # 深交所ETF
            # 尝试使用指数成分股函数获取ETF成分股
            # 注意：这里使用ETF对应的指数代码，可能需要映射关系
            # 例如：159207对应中证红利指数000922
            index_code = '000922'  # 中证红利指数
            if etf_code == '159207':
                etf_stock_cons = ak.index_stock_cons(symbol=index_code)
                print(f"获取指数 {index_code} (对应ETF {etf_code})成分股...")
            else:
                # 其他ETF可能需要不同的指数代码
                print(f"暂不支持ETF {etf_code}的成分股获取")
                return []
        elif etf_code.startswith('5'):  # 上交所ETF
            # 上交所ETF可能需要其他方法
            print(f"暂不支持ETF {etf_code}的成分股获取")
            return []
        else:
            print(f"不支持的ETF代码格式: {etf_code}")
            return []
        
        if etf_stock_cons.empty:
            print(f"获取ETF {etf_code} 成分股失败")
            return []
        
        # 提取股票代码
        if '成分券代码' in etf_stock_cons.columns:
            stock_codes_col = '成分券代码'
        elif '品种代码' in etf_stock_cons.columns:
            stock_codes_col = '品种代码'
        else:
            print(f"无法识别成分股代码列: {etf_stock_cons.columns}")
            return []
            
        stock_codes = etf_stock_cons[stock_codes_col].tolist()
        
        # 提取股票名称
        if '成分券名称' in etf_stock_cons.columns:
            stock_names_col = '成分券名称'
        elif '品种名称' in etf_stock_cons.columns:
            stock_names_col = '品种名称'
        else:
            stock_names_col = None
            
        # 格式化股票代码和名称
        formatted_stocks = []
        for i, code in enumerate(stock_codes):
            if isinstance(code, str) and len(code) >= 6:
                formatted_code = code[-6:]
                stock_name = etf_stock_cons.iloc[i][stock_names_col] if stock_names_col else "未知"
                formatted_stocks.append({
                    'code': formatted_code,
                    'name': stock_name
                })
        
        print(f"成功获取ETF {etf_code} 成分股，共 {len(formatted_stocks)} 只")
        return formatted_stocks
    except Exception as e:
        print(f"获取ETF {etf_code} 成分股失败: {e}")
        return []

def calculate_ma_signals(stock_code, start_date, end_date):
    """计算移动平均线信号"""
    # 获取历史数据
    df = get_stock_history(stock_code, start_date, end_date)
    if df.empty:
        return None
    
    # 确保日期列是datetime类型
    if '日期' in df.columns:
        df['日期'] = pd.to_datetime(df['日期'])
    elif 'trade_date' in df.columns:
        df['日期'] = pd.to_datetime(df['trade_date'])
        
    # 计算移动平均线
    ma_period = STRATEGY_CONFIG['ma_period']
    df[f'MA{ma_period}'] = df['收盘'].rolling(window=ma_period).mean()
    
    # 计算价格与MA的比例
    df['价格/MA比例'] = (df['收盘'] / df[f'MA{ma_period}'] - 1) * 100
    
    # 计算买入卖出信号
    price_ma_ratio = STRATEGY_CONFIG['price_ma_ratio']
    df['买入信号'] = df['价格/MA比例'] <= -price_ma_ratio
    df['卖出信号'] = df['价格/MA比例'] >= price_ma_ratio
    
    return df

def backtest_strategy(stock_code, stock_name, start_date, end_date):
    """回测策略"""
    # 计算信号
    df = calculate_ma_signals(stock_code, start_date, end_date)
    if df is None or df.empty or len(df) < STRATEGY_CONFIG['ma_period']:
        return None
    
    # 初始化回测结果
    initial_capital = 100000  # 初始资金10万
    position = 0  # 持仓数量
    cash = initial_capital  # 现金
    trades = []  # 交易记录
    
    # 回测
    for i in range(STRATEGY_CONFIG['ma_period'], len(df)):
        date = df.iloc[i]['日期']
        price = df.iloc[i]['收盘']
        buy_signal = df.iloc[i]['买入信号']
        sell_signal = df.iloc[i]['卖出信号']
        
        # 买入信号
        if buy_signal and position == 0 and cash > 0:
            # 计算可买入的股数（假设可以买入零碎股）
            shares_to_buy = cash / price
            position = shares_to_buy
            cash = 0
            trades.append({
                '日期': date,
                '类型': '买入',
                '价格': price,
                '数量': shares_to_buy,
                '资金': cash,
                '持仓价值': position * price,
                '总资产': cash + position * price
            })
        
        # 卖出信号
        elif sell_signal and position > 0:
            # 卖出全部持仓
            cash = position * price
            position = 0
            trades.append({
                '日期': date,
                '类型': '卖出',
                '价格': price,
                '数量': 0,
                '资金': cash,
                '持仓价值': 0,
                '总资产': cash
            })
    
    # 计算最终资产（如果还有持仓，按最后一天的收盘价计算）
    final_price = df.iloc[-1]['收盘']
    final_assets = cash + position * final_price
    
    # 计算收益率
    returns = (final_assets - initial_capital) / initial_capital * 100
    
    # 找出最近的买入点和卖出点
    recent_buy_points = []
    recent_sell_points = []
    
    # 获取最近30天的数据
    recent_df = df.tail(30)
    for i in range(len(recent_df)):
        if recent_df.iloc[i]['买入信号']:
            recent_buy_points.append({
                '日期': recent_df.iloc[i]['日期'],
                '价格': recent_df.iloc[i]['收盘'],
                '偏离度': recent_df.iloc[i]['价格/MA比例']
            })
        if recent_df.iloc[i]['卖出信号']:
            recent_sell_points.append({
                '日期': recent_df.iloc[i]['日期'],
                '价格': recent_df.iloc[i]['收盘'],
                '偏离度': recent_df.iloc[i]['价格/MA比例']
            })
    
    return {
        '代码': stock_code,
        '名称': stock_name,
        '初始资金': initial_capital,
        '最终资产': final_assets,
        '收益率(%)': returns,
        '交易次数': len(trades),
        '交易记录': trades,
        '最近买入点': recent_buy_points,
        '最近卖出点': recent_sell_points,
        '历史数据': df
    }

def optimize_parameters(stock_code, stock_name, start_date, end_date):
    """优化策略参数"""
    best_return = -float('inf')
    best_params = {}
    results = []
    
    # 获取历史数据
    df_history = get_stock_history(stock_code, start_date, end_date)
    if df_history.empty or len(df_history) < 180:  # 确保有足够的数据进行优化
        return None
    
    # 确保日期列是datetime类型
    if '日期' in df_history.columns:
        df_history['日期'] = pd.to_datetime(df_history['日期'])
    elif 'trade_date' in df_history.columns:
        df_history['日期'] = pd.to_datetime(df_history['trade_date'])
        df_history.rename(columns={'trade_date': '日期'}, inplace=True)
    
    # 遍历不同的参数组合
    for ma_period in STRATEGY_CONFIG['optimize_params']['ma_periods']:
        # 计算移动平均线
        df_history[f'MA{ma_period}'] = df_history['收盘'].rolling(window=ma_period).mean()
        
        for price_ma_ratio in STRATEGY_CONFIG['optimize_params']['price_ma_ratios']:
            # 计算价格与MA的比例
            df_history['价格/MA比例'] = (df_history['收盘'] / df_history[f'MA{ma_period}'] - 1) * 100
            
            # 计算买入卖出信号
            df_history['买入信号'] = df_history['价格/MA比例'] <= -price_ma_ratio
            df_history['卖出信号'] = df_history['价格/MA比例'] >= price_ma_ratio
            
            # 初始化回测结果
            initial_capital = 100000  # 初始资金10万
            position = 0  # 持仓数量
            cash = initial_capital  # 现金
            trades = []  # 交易记录
            
            # 回测
            for i in range(ma_period, len(df_history)):
                date = df_history.iloc[i]['日期']
                price = df_history.iloc[i]['收盘']
                buy_signal = df_history.iloc[i]['买入信号']
                sell_signal = df_history.iloc[i]['卖出信号']
                
                # 买入信号
                if buy_signal and position == 0 and cash > 0:
                    # 计算可买入的股数（假设可以买入零碎股）
                    shares_to_buy = cash / price
                    position = shares_to_buy
                    cash = 0
                    trades.append({
                        '日期': date,
                        '类型': '买入',
                        '价格': price
                    })
                
                # 卖出信号
                elif sell_signal and position > 0:
                    # 卖出全部持仓
                    cash = position * price
                    position = 0
                    trades.append({
                        '日期': date,
                        '类型': '卖出',
                        '价格': price
                    })
            
            # 计算最终资产（如果还有持仓，按最后一天的收盘价计算）
            final_price = df_history.iloc[-1]['收盘']
            final_assets = cash + position * final_price
            
            # 计算收益率
            returns = (final_assets - initial_capital) / initial_capital * 100
            
            # 记录结果
            result = {
                'MA周期': ma_period,
                '价格/MA比例(%)': price_ma_ratio,
                '收益率(%)': returns,
                '交易次数': len(trades)
            }
            results.append(result)
            
            # 更新最佳参数
            if returns > best_return:
                best_return = returns
                best_params = {
                    'ma_period': ma_period,
                    'price_ma_ratio': price_ma_ratio
                }
    
    # 转换为DataFrame
    df_results = pd.DataFrame(results)
    
    return {
        '代码': stock_code,
        '名称': stock_name,
        '最佳参数': best_params,
        '最佳收益率(%)': best_return,
        '参数优化结果': df_results
    }

def plot_strategy_results(backtest_result, output_folder):
    """绘制策略结果图表"""
    if not backtest_result or 'historical_data' not in backtest_result:
        return
    
    stock_code = backtest_result['代码']
    stock_name = backtest_result['名称']
    df = backtest_result['历史数据']
    
    # 创建图表
    plt.figure(figsize=(12, 8))
    
    # 绘制价格和移动平均线
    plt.subplot(2, 1, 1)
    plt.plot(df['日期'], df['收盘'], label='收盘价', color='blue')
    plt.plot(df['日期'], df[f'MA{STRATEGY_CONFIG["ma_period"]}'], label=f'MA{STRATEGY_CONFIG["ma_period"]}', color='red')
    
    # 标记买入点和卖出点
    buy_dates = [trade['日期'] for trade in backtest_result['交易记录'] if trade['类型'] == '买入']
    buy_prices = [trade['价格'] for trade in backtest_result['交易记录'] if trade['类型'] == '买入']
    sell_dates = [trade['日期'] for trade in backtest_result['交易记录'] if trade['类型'] == '卖出']
    sell_prices = [trade['价格'] for trade in backtest_result['交易记录'] if trade['类型'] == '卖出']
    
    plt.scatter(buy_dates, buy_prices, marker='^', color='green', s=100, label='买入信号')
    plt.scatter(sell_dates, sell_prices, marker='v', color='red', s=100, label='卖出信号')
    
    plt.title(f'{stock_code} {stock_name} 策略回测结果')
    plt.ylabel('价格')
    plt.legend()
    plt.grid(True)
    
    # 绘制价格/MA比例
    plt.subplot(2, 1, 2)
    plt.plot(df['日期'], df['价格/MA比例'], label='价格/MA比例(%)', color='purple')
    plt.axhline(y=STRATEGY_CONFIG['price_ma_ratio'], color='r', linestyle='--', label=f'+{STRATEGY_CONFIG["price_ma_ratio"]}% (卖出线)')
    plt.axhline(y=-STRATEGY_CONFIG['price_ma_ratio'], color='g', linestyle='--', label=f'-{STRATEGY_CONFIG["price_ma_ratio"]}% (买入线)')
    plt.axhline(y=0, color='k', linestyle='-')
    
    plt.title(f'价格相对于MA{STRATEGY_CONFIG["ma_period"]}的偏离比例')
    plt.ylabel('偏离比例(%)')
    plt.legend()
    plt.grid(True)
    
    # 调整布局
    plt.tight_layout()
    
    # 保存图表
    file_name = f"{stock_code}_{stock_name}_策略回测图.png"
    file_path = os.path.join(output_folder, file_name)
    plt.savefig(file_path)
    plt.close()
    
    print(f"策略回测图表已保存至 {file_path}")

def save_results(results, file_name, output_folder):
    """保存结果到文件"""
    # 创建输出文件夹
    folder_path = os.path.join(os.path.dirname(__file__), output_folder)
    os.makedirs(folder_path, exist_ok=True)
    
    # 生成文件路径
    file_path = os.path.join(folder_path, file_name)
    
    # 保存到文件
    with open(file_path, 'w', encoding='utf-8') as f:
        f.write(results)
    
    print(f"\n数据已保存至 {file_path}")

def generate_report(backtest_results, optimize_results=None):
    """生成策略报告"""
    report = []
    report.append("# 红利白马股策略回测报告")
    report.append(f"## 回测时间: {STRATEGY_CONFIG['backtest_start_year']}年 至 {STRATEGY_CONFIG['backtest_end_year']}年")
    report.append(f"## 策略参数")
    report.append(f"- 移动平均线周期: {STRATEGY_CONFIG['ma_period']}日")
    report.append(f"- 价格/MA偏离比例: {STRATEGY_CONFIG['price_ma_ratio']}%")
    report.append(f"- 最小市盈率: {STRATEGY_CONFIG['min_pe']}")
    report.append(f"- 最小股息率: {STRATEGY_CONFIG['min_dividend_yield']}%")
    report.append("")
    
    # 添加回测结果
    report.append("## 回测结果")
    for result in backtest_results:
        report.append(f"### {result['代码']} {result['名称']}")
        report.append(f"- 初始资金: {result['初始资金']:.2f}元")
        report.append(f"- 最终资产: {result['最终资产']:.2f}元")
        report.append(f"- 收益率: {result['收益率(%)']:.2f}%")
        report.append(f"- 交易次数: {result['交易次数']}")
        report.append(f"- 市盈率: {result.get('市盈率', '未知')}")
        report.append(f"- 股息率: {result.get('股息率(%)', '未知')}%")
        
        # 添加最近的买入点和卖出点
        if result['最近买入点']:
            report.append("\n#### 最近买入点")
            report.append("| 日期 | 价格 | 相对MA偏离度 |")
            report.append("| ---- | ---- | ------------ |")
            for point in result['最近买入点']:
                date_str = point['日期'].strftime('%Y-%m-%d') if hasattr(point['日期'], 'strftime') else str(point['日期'])
                deviation = point.get('偏离度', '-')
                if deviation != '-':
                    deviation = f"{deviation:.2f}%"
                report.append(f"| {date_str} | {point['价格']:.2f}元 | {deviation} |")
        
        if result['最近卖出点']:
            report.append("\n#### 最近卖出点")
            report.append("| 日期 | 价格 | 相对MA偏离度 |")
            report.append("| ---- | ---- | ------------ |")
            for point in result['最近卖出点']:
                date_str = point['日期'].strftime('%Y-%m-%d') if hasattr(point['日期'], 'strftime') else str(point['日期'])
                deviation = point.get('偏离度', '-')
                if deviation != '-':
                    deviation = f"{deviation:.2f}%"
                report.append(f"| {date_str} | {point['价格']:.2f}元 | {deviation} |")
        
        report.append("")
    
    # 添加参数优化结果
    if optimize_results:
        report.append("## 参数优化结果")
        for result in optimize_results:
            report.append(f"### {result['代码']} {result['名称']}")
            report.append(f"- 最佳参数: MA周期={result['最佳参数']['ma_period']}, 价格/MA比例={result['最佳参数']['price_ma_ratio']}%")
            report.append(f"- 最佳收益率: {result['最佳收益率(%)']:.2f}%")
            report.append("")
            
            # 添加参数优化表格
            report.append("| MA周期 | 价格/MA比例(%) | 收益率(%) | 交易次数 |")
            report.append("| ------ | ------------- | -------- | -------- |")
            
            # 按收益率排序
            df_sorted = result['参数优化结果'].sort_values(by='收益率(%)', ascending=False)
            for _, row in df_sorted.head(10).iterrows():
                report.append(f"| {row['MA周期']} | {row['价格/MA比例(%)']} | {row['收益率(%)']:.2f} | {row['交易次数']} |")
            
            report.append("")
    
    # 添加结论
    report.append("## 策略结论")
    report.append("1. 该策略通过筛选高股息率和合理市盈率的红利白马股，结合移动平均线偏离度进行买卖决策。")
    report.append("2. 策略在回测期间表现出一定的盈利能力，但不同股票表现差异较大。")
    report.append("3. 参数优化结果表明，不同股票的最佳参数存在差异，建议针对具体股票进行个性化参数调整。")
    report.append("4. 该策略适合长期投资者，可以在市场波动中获取稳定的股息收益和资本增值。")
    
    return "\n".join(report)

def parse_arguments():
    """解析命令行参数"""
    parser = argparse.ArgumentParser(description="红利白马股策略回测")
    parser.add_argument("--min-pe", type=float, help="最小市盈率")
    parser.add_argument("--min-dividend", type=float, help="最小股息率(%)")
    parser.add_argument("--ma-period", type=int, help="移动平均线周期")
    parser.add_argument("--price-ma-ratio", type=float, help="价格与MA的偏离比例(%)")
    parser.add_argument("--start-year", type=int, help="回测开始年份")
    parser.add_argument("--end-year", type=int, help="回测结束年份")
    parser.add_argument("--etf", nargs='+', help="红利ETF代码列表")
    parser.add_argument("--stocks", nargs='+', help="手动输入的股票代码列表")
    parser.add_argument("--optimize", action="store_true", help="是否进行参数优化")
    parser.add_argument("--output", help="输出文件夹")
    
    args = parser.parse_args()
    
    # 更新配置
    if args.min_pe:
        STRATEGY_CONFIG['min_pe'] = args.min_pe
    if args.min_dividend:
        STRATEGY_CONFIG['min_dividend_yield'] = args.min_dividend
    if args.ma_period:
        STRATEGY_CONFIG['ma_period'] = args.ma_period
    if args.price_ma_ratio:
        STRATEGY_CONFIG['price_ma_ratio'] = args.price_ma_ratio
    if args.start_year:
        STRATEGY_CONFIG['backtest_start_year'] = args.start_year
    if args.end_year:
        STRATEGY_CONFIG['backtest_end_year'] = args.end_year
    if args.etf:
        STRATEGY_CONFIG['etf_codes'] = args.etf
    if args.stocks:
        STRATEGY_CONFIG['manual_stock_codes'] = args.stocks
    if args.output:
        STRATEGY_CONFIG['output_folder'] = args.output

def main():
    """主函数"""
    # 解析命令行参数
    parse_arguments()
    
    start_time = time.time()
    print(f"开始分析: {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
    print(f"策略参数: MA{STRATEGY_CONFIG['ma_period']}, 价格/MA偏离比例: {STRATEGY_CONFIG['price_ma_ratio']}%")
    print(f"筛选条件: 市盈率>={STRATEGY_CONFIG['min_pe']}, 股息率>={STRATEGY_CONFIG['min_dividend_yield']}%")
    print(f"回测时间: {STRATEGY_CONFIG['backtest_start_year']}年 至 {STRATEGY_CONFIG['backtest_end_year']}年")
    
    # 创建输出文件夹
    output_folder = os.path.join(os.path.dirname(__file__), STRATEGY_CONFIG['output_folder'])
    os.makedirs(output_folder, exist_ok=True)
    
    try:
        # 获取股票池
        stock_pool = []
        
        # 1. 从ETF获取成分股
        if 'etf_codes' in STRATEGY_CONFIG:
          for etf_code in STRATEGY_CONFIG['etf_codes']:
            print(f"\n获取ETF {etf_code} 成分股...")
            etf_stocks = get_etf_constituents(etf_code)
            if etf_stocks:
                print(f"获取到 {len(etf_stocks)} 只ETF成分股")
                # 将字典列表转换为代码列表
                for stock in etf_stocks:
                    stock_pool.append(stock['code'])
        
        # 2. 添加手动输入的股票代码
        stock_pool.extend(STRATEGY_CONFIG['manual_stock_codes'])
        
        # 去重
        stock_pool = list(set(stock_pool))
        print(f"\n股票池共有 {len(stock_pool)} 只股票")
        
        # 3. 如果股票池为空，则通过筛选条件获取股票
        if not stock_pool:
            print("\n股票池为空，通过市盈率和股息率筛选股票...")
            filtered_stocks = filter_stocks_by_criteria()
            if not filtered_stocks.empty:
                stock_pool = filtered_stocks['代码'].tolist()
                print(f"筛选出 {len(stock_pool)} 只符合条件的股票")
        
        # 如果股票池仍为空，则退出
        if not stock_pool:
            print("没有符合条件的股票，程序结束")
            return
        
        # 设置回测时间范围
        start_date = f"{STRATEGY_CONFIG['backtest_start_year']}0101"
        end_date = f"{STRATEGY_CONFIG['backtest_end_year']}1231"
        
        # 回测结果
        backtest_results = []
        optimize_results = []
        
        # 对每只股票进行回测
        for stock_code in stock_pool:
            print(f"\n开始分析股票 {stock_code}...")
            
            # 获取股票名称
            try:
                stock_info = get_stock_zh_a_spot_em_safe()
                stock_info = stock_info[stock_info['代码'] == stock_code]
                if not stock_info.empty:
                    stock_name = stock_info.iloc[0]['名称']
                else:
                    stock_name = stock_code
            except:
                stock_name = stock_code
            
            # 回测策略
            result = backtest_strategy(stock_code, stock_name, start_date, end_date)
            if result:
                backtest_results.append(result)
                print(f"{stock_code} {stock_name} 回测完成，收益率: {result['收益率(%)']:.2f}%")
                
                # 绘制结果图表
                if STRATEGY_CONFIG['plot_results']:
                    plot_strategy_results(result, output_folder)
            
            # 参数优化
            optimize = '--optimize' in sys.argv
            if optimize:
                print(f"\n开始优化 {stock_code} {stock_name} 的策略参数...")
                optimize_result = optimize_parameters(stock_code, stock_name, start_date, end_date)
                if optimize_result:
                    optimize_results.append(optimize_result)
                    print(f"{stock_code} {stock_name} 参数优化完成，最佳收益率: {optimize_result['最佳收益率(%)']:.2f}%")
        
        # 生成报告
        if backtest_results:
            report = generate_report(backtest_results, optimize_results if optimize else None)
            
            # 保存报告
            today = datetime.datetime.now().strftime('%Y%m%d_%H%M%S')
            report_file_name = f"红利白马股策略报告_{today}.md"
            save_results(report, report_file_name, output_folder)
    
    except KeyboardInterrupt:
        print("\n程序被用户中断")
    except Exception as e:
        print(f"\n程序执行出错: {e}")
    finally:
        end_time = time.time()
        elapsed_time = end_time - start_time
        print(f"\n分析完成，耗时: {elapsed_time:.2f} 秒 ({elapsed_time/60:.2f} 分钟)")
        print(f"结束时间: {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")

if __name__ == "__main__":
    import sys
    main()